// Copyright (c) Microsoft Corporation. All rights reserved.

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Net;
using System.IO;
using System.Web.Services.Protocols;
using System.Xml;
using Microsoft.Teamfoundation.Migration.VersionControl.SharePoint.Versions;
using System.Xml.Serialization;
using System.Diagnostics;
using Microsoft.TeamFoundation.Migration.Toolkit;
using Microsoft.TeamFoundation.Migration.Toolkit.VC;

namespace Microsoft.TeamFoundation.Migration.VersionControl.Wss
{
    [Serializable]
    public sealed class WssMigrationItem : IMigrationItem
    {
        string m_canonicalUrl;
        string m_downloadUrl;
        int m_revision;
        int m_fileId;

        bool m_isDeleted;

        HttpStatusCode m_downloadStatus;

        [NonSerialized]
        VersionControlSession m_session;

        // needed for XML serialization
        public WssMigrationItem()
            : this(null, 0)
        {
        }

        public WssMigrationItem(string canonicalUrl, int revision)
            : this(canonicalUrl, revision, null)
        {
        }

        public WssMigrationItem(string canonicalUrl, int revision, string downloadUrl)
            : this(-1, canonicalUrl, revision, downloadUrl)
        {
        }

        public WssMigrationItem(int fileId, string canonicalUrl, int revision, string downloadUrl)
        {
            m_downloadStatus = (HttpStatusCode)0;

            CanonicalUrl = canonicalUrl;
            m_revision = revision;
            m_downloadUrl = downloadUrl;
            m_fileId = fileId;
        }

        [XmlElement("fid")]
        public int FileId
        {
            get
            {
                return m_fileId;
            }
            set
            {
                m_fileId = value;
            }
        }


        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings"), XmlElement("curl")]
        public string CanonicalUrl
        {
            get
            {
                return m_canonicalUrl;
            }
            set
            {
                m_canonicalUrl = value;
            }
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings"), XmlElement("drul")]
        public string DownloadUrl
        {
            get
            {
                return m_downloadUrl;
            }
            set
            {
                m_downloadUrl = value;
            }
        }

        [XmlElement("rev")]
        public int Revision
        {
            get
            {
                return m_revision;
            }
            set
            {
                m_revision = value;
            }
        }

        [XmlIgnore]
        public VersionControlSession Session
        {
            get
            {
                return m_session;
            }
            set
            {
                m_session = value;
            }
        }

        public HttpStatusCode DownloadStatus
        {
            get
            {
                return m_downloadStatus;
            }
        }

        public bool IsDeleted
        {
            get
            {
                return m_isDeleted;
            }
        }

        #region IMigrationItem Members

        public void Download(string localPath)
        {
            if (string.IsNullOrEmpty(m_downloadUrl))
            {
                getDownloadUrlForWssItem();
            }

            if (!string.IsNullOrEmpty(m_downloadUrl))
            {
                using (WebClient wc = new WebClient())
                {
                    wc.UseDefaultCredentials = true;

                    try
                    {
                        TfsUtil.EnsurePathToFileExists(localPath);
                    }
                    catch (PathTooLongException ptl)
                    {
                        throw new WssItemNotDownloadedException(ptl);
                    }

                    try
                    {
                        wc.DownloadFile(m_downloadUrl, localPath);
                        m_downloadStatus = HttpStatusCode.OK;
                        return;
                    }
                    catch (WebException we)
                    {
                        TraceManager.WriteLine(TraceManager.Engine,
                            string.Format(CultureInfo.InvariantCulture, "caught web exception downloading file: {0}", m_downloadUrl));

                        TraceManager.WriteLine(TraceManager.Engine, we.ToString());

                        if (we.Status == WebExceptionStatus.ProtocolError)
                        {
                            m_downloadStatus = ((HttpWebResponse)we.Response).StatusCode;
                            return;
                        }

                        throw;
                    }
                }
            }
        }

        public string DisplayName
        {
            get
            {
                return string.Format(CultureInfo.InvariantCulture, "{0} (revision {1})",
                    CanonicalUrl, m_revision);
            }
        }

        #endregion


        private void getDownloadUrlForWssItem()
        {
            using (Versions vers = Util.GetVersionsServiceForUrl(CanonicalUrl, this.Session))
            {
                if (vers != null)
                {
                    XmlNode versions = null;

                    try
                    {
                        versions = vers.GetVersions(CanonicalUrl);
                    }
                    catch (WebException se)
                    {
                        // this exception is a warning until we determine the 404 status
                        TraceManager.TraceWarning("Error getting versions for {0}", CanonicalUrl);
                        TraceManager.TraceWarning(se.ToString());

                        // if the file is completly 404 then we can assume 
                        // it is a deleted item.
                        if (!downloadAttemptIs404(CanonicalUrl))
                        {
                            // otherwise we should try again a few times.  (we checked for 401 during 
                            // analysis but maybe should check for it here too)
                            throw new VersionControlMigrationException(se, true);
                        }

                        m_isDeleted = true;
                    }
                    catch (SoapException se)
                    {
                        // this exception is a warning until we determine the 404 status
                        TraceManager.TraceWarning("Error getting versions for {0}", CanonicalUrl);
                        TraceManager.TraceWarning(se.ToString());

                        // if the file is completly 404 then we can assume 
                        // it is a deleted item.
                        if (!downloadAttemptIs404(CanonicalUrl))
                        {
                            // otherwise we should try again a few times.  (we checked for 401 during 
                            // analysis but maybe should check for it here too)
                            throw new VersionControlMigrationException(se, true);
                        }

                        m_isDeleted = true;
                    }

                    if (!m_isDeleted && versions != null)
                    {
                        Document docVersion = new Document(versions, ((Session)m_session).Culture);

                        foreach (DocumentVersion dv in docVersion.Versions)
                        {
                            if (dv.Version == m_revision)
                            {
                                m_downloadUrl = dv.Url;
                                break;
                            }
                        }

                        // if we got this far then we know the document exists but the 
                        // specific version is deleted.  We can't move this to the outer
                        // scope because a null downloadUrl might just mean the area is
                        // no longer mapped - in this case it means we are mapped but
                        // we've detected a deleted item.
                        m_isDeleted = (m_downloadUrl == null);
                    }
                }
            }
        }

        private static bool downloadAttemptIs404(string downloadUrl)
        {
            bool is404 = false;

            try
            {
                using (WebClient wc = new WebClient())
                {
                    wc.UseDefaultCredentials = true;
                    using (Stream readStream = wc.OpenRead(downloadUrl))
                    {
                        // this is enough to verify the Url is valid
                    }
                }
            }
            catch (WebException we)
            {
                // If we get a 401 error skip the folder
                if (we.Status == WebExceptionStatus.ProtocolError)
                {
                    if (
                        (((HttpWebResponse)we.Response).StatusCode == HttpStatusCode.NotFound) ||
                        (((HttpWebResponse)we.Response).StatusCode == HttpStatusCode.BadRequest))
                    {
                        is404 = true;
                    }
                }
            }

            return is404;
        }

        public override string ToString()
        {
            return DisplayName;
        }

        public static WssMigrationItem FromString(string blob, ChangeGroupingMananger manager)
        {
            if (manager == null)
            {
                throw new ArgumentNullException("manager");
            }

            XmlSerializer serializer = new XmlSerializer(typeof(WssMigrationItem));

            using(StringReader strReader = new StringReader(blob))
            using (XmlReader xmlReader = XmlReader.Create(strReader))
            {
                WssMigrationItem item = (WssMigrationItem)serializer.Deserialize(xmlReader);
                item.Session = manager.Session;
                return item;
            }
        }
    }
}
